package com.tanx.cqrs.saga;

import com.tanx.cqrs.event.Event;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


@Data
@NoArgsConstructor
@AllArgsConstructor
public class SagaHandlerInstance {
    private Saga bean;
    private Method method;
    private SagaStoreRepository repository;
    private long timeOut;

    /**
     * 调用saga处理程序
     *
     * @param event 事件
     */
    public void invoke(Event event) {
        //TODO:如何进行处理,saga是否有状态
        //有状态:分布式情况下如何处理
        //无状态:saga和普通事件有何区别?

        //开始调用saga,处理saga中start部分
        SagaInfo info = beginSaga(event);
        //处理调用部分
        updateSagaEventList(event, info);
        //处理saga中endSaga部分
        endSaga(event);
    }

    private void endSaga(Event event) {
        if (end()) {
            repository.endSaga(event.getSagaId(), this, event);
        }
    }

    private void updateSagaEventList(Event event, SagaInfo info) {
        //是否超时
        if (info.timeout(timeOut)) {
            invokeTimeOutMethod();
            return;
        }
        //是否调用过了此方法
        if (info.contains(event)) {
            return;
        }
        invokeMethod(event);
        repository.updateSagaEventList(event.getSagaId(), bean.getClass(), event);
    }

    /**
     * 处理超时
     */
    private void invokeTimeOutMethod() {
        Method[] methods = bean.getClass().getMethods();
        Method timeoutMethod = null;
        for (Method methodTemp : methods) {
            SagaTimeout annotation = methodTemp.getAnnotation(SagaTimeout.class);
            if (annotation != null) {
                timeoutMethod = methodTemp;
                break;
            }
        }
        if (timeoutMethod == null) {
            throw new RuntimeException("未找到:" + bean.getClass() + "中的超时处理方法,请检查");
        }

        try {
            method.invoke(bean, (Object) null);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    private SagaInfo beginSaga(Event event) {
        if (begin()) {
            event.setSagaId(repository.newUuid());
            return repository.insertSaga(event.getSagaId(), this, event);
        } else {
            return repository.findSaga(event.getSagaId(), bean.getClass());
        }
    }

    private void invokeMethod(Event event) {
        try {
            Saga o = bean.getClass().newInstance();
            o.setUuid(event.getSagaId());
            method.invoke(o, event);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private boolean end() {
        EndSaga annotation = method.getAnnotation(EndSaga.class);
        return annotation != null;
    }

    private boolean begin() {
        SagaStart annotation = method.getAnnotation(SagaStart.class);
        return annotation != null;
    }
}
